package com.credithc.signature.manager.service.syncsign;

import com.alibaba.fastjson.JSONObject;
import com.cfca.ra.cs.generated.CertApplyVO;
import com.credithc.signature.common.constant.CoreConstant;
import com.credithc.signature.common.mapper.CertMapper;
import com.credithc.signature.common.mapper.SealInfoMapper;
import com.credithc.signature.common.po.*;
import com.credithc.signature.common.ro.core.CoreRequet;
import com.credithc.signature.common.ro.sign.SealCertInfoRo;
import com.credithc.signature.common.service.*;
import com.credithc.signature.common.to.CFCASignParam;
import com.credithc.signature.common.to.CFCASignResult;
import com.credithc.signature.common.to.CFCASignTO;
import com.credithc.signature.common.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 描述：同步调用签章服务入口<br/>
 * 作者：sunyang
 * 创建：2019/7/1 15:32
 * 版本：v1.0.0<br/>
 */
@Slf4j
@Service
public class SignatureServiceSync {

    @Value("${signature.snapSignPdf.path:/data/sync_temp/sign-pdf/}")
    private String snapPDFPath;

    @Value("${signature.maxBatchSignNum:160}")
    private Integer maxBatchSignNum;

    @Autowired
    private CFCASignService cfcaSignService;

    @Autowired
    private SealInfoService sealInfoService;

    @Autowired
    private TemplateService templateService;

    @Autowired
    private OfficialSealTemplateService officialSealTemplateService;

    @Autowired
    private CertInfoService certInfoService;

    @Autowired
    private SignatureLogService signatureLogService;

    @Autowired
    private SealInfoMapper sealInfoMapper;

    @Autowired
    CertMapper certMapper;


    /**
     * 签章服务
     *
     * @param coreRequet
     * @param noSignPdfPath
     * @param certInfos
     * @return
     * @throws Exception
     */
    public String signature(CoreRequet coreRequet, String noSignPdfPath, List<Map<String, Object>> certInfos, List<String> sealCodes) throws Exception {

        // 获取要签章pdf文件路径
        File file = new File(noSignPdfPath);
        String date = file.getParentFile().getName();
        String signPdfName = CoreConstant.SNAP_PDF_PREFIX + coreRequet.getRequestSn() + CoreConstant.PDF_EXT;
        String signSnapPDFPath = getSignSnapPDFPath(signPdfName, date);

        Template template = templateService.selectByTemplateNo(coreRequet.getTemplateNo());
        Integer sealType = template.getSealType();
        if (sealType.equals(CoreConstant.PERSONAL)) {
            // 签私章
            List<CFCASignTO> personalSealInfoList = getPersonalSealInfoList(certInfos, coreRequet.getRequestSn());
            signPDF(coreRequet.getRequestSn(), noSignPdfPath, signSnapPDFPath, personalSealInfoList, false);
        } else if (sealType.equals(CoreConstant.OFFICAL)) {
            // 签公章
            List<CFCASignTO> officalSealInfoList = getOfficalSealInfoList(coreRequet.getRequestSn(), coreRequet.getTemplateNo(), sealCodes);
            if (officalSealInfoList == null || officalSealInfoList.isEmpty()) {
                copy(noSignPdfPath, signSnapPDFPath);
            } else {
                signPDF(coreRequet.getRequestSn(), noSignPdfPath, signSnapPDFPath, officalSealInfoList, true);
            }
        } else if (sealType.equals(CoreConstant.OFFICAL_PERSONAL)) {
            List<CFCASignTO> officalSealInfoList = getOfficalSealInfoList(coreRequet.getRequestSn(), coreRequet.getTemplateNo(), sealCodes);
            List<CFCASignTO> personalSealInfoList = getPersonalSealInfoList(certInfos, coreRequet.getRequestSn());
            // 先盖私章
            signPDF(coreRequet.getRequestSn(), noSignPdfPath, signSnapPDFPath, personalSealInfoList, false);
            // 再盖公章
            signPDF(coreRequet.getRequestSn(), signSnapPDFPath, signSnapPDFPath, officalSealInfoList, true);
        } else {
            log.error("－消息－:requestSn={},非法签章类型{}", coreRequet.getRequestSn(), sealType);
            throw new Exception("非法签章类型");
        }
        return signSnapPDFPath;
    }

    /**
     * 复制文件
     *
     * @param url1
     * @param url2
     * @throws Exception
     */
    public void copy(String url1, String url2) throws Exception {
        File file = new File(url1);
        if (file.exists()) {
            FileInputStream in = new FileInputStream(new File(url1));
            FileOutputStream out = new FileOutputStream(new File(url2));
            try {
                byte[] buff = new byte[512];
                int n = 0;
                log.debug("复制文件:\n源路径：" + url1 + "\n" + "目标路径:" + url2);
                while ((n = in.read(buff)) != -1) {
                    out.write(buff, 0, n);
                }
                out.flush();
                log.debug("文件复制完成");
            } catch (Exception e) {
                throw new IOException(e);
            } finally {
                in.close();
                out.close();
            }
        } else {
            log.error("源文件不存在");
        }
    }

    /**
     * 签章
     *
     * @param requestSn
     * @param noSignPdfPath
     * @param sealInfoList
     */
    private void signPDF(String requestSn, String noSignPdfPath, String signSnapPDFPath,
                         List<CFCASignTO> sealInfoList, boolean isOfficial) throws Exception {
        if (sealInfoList.size() > maxBatchSignNum) {
            forkSign(requestSn, noSignPdfPath, signSnapPDFPath, isOfficial, sealInfoList);
        } else {
            singleSign(requestSn, noSignPdfPath, signSnapPDFPath, sealInfoList, isOfficial);
        }
    }

    /**
     * 一次批量签署公私章
     *
     * @param requestSn
     * @param noSignPdfPath
     * @param signSnapPDFPath
     * @param sealInfoList
     * @param isOfficialSign
     */
    private void singleSign(String requestSn, String noSignPdfPath, String signSnapPDFPath, List<CFCASignTO> sealInfoList,
                            boolean isOfficialSign) throws Exception {

        CFCASignParam cfcaSignParam = new CFCASignParam(sealInfoList, requestSn, noSignPdfPath, signSnapPDFPath);
        try {
            CFCASignResult cfcaSignResult;
            if (isOfficialSign) {
                cfcaSignResult = cfcaSignService.batchOfficialSign(cfcaSignParam);
            } else {
                cfcaSignResult = cfcaSignService.batchPersonalSign(cfcaSignParam);
            }
            if (!cfcaSignResult.getIsSuccess()) {
                log.error("－消息－:requestSn={},签章结果:{}", requestSn, cfcaSignResult.getResultMsg());
                throw new Exception(cfcaSignResult.getResultMsg());
            }
        } catch (Exception e) {
            log.error("－消息－:requestSn={},调用三方签章失败", requestSn, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 分批次签署私章
     *
     * @param requestSn
     * @param noSignPdfPath
     * @param isOfficial
     */
    private void forkSign(String requestSn, String noSignPdfPath, String signSnapPDFPath, boolean isOfficial
            , List<CFCASignTO> sealInfoList) throws Exception {
        int totalSealNum = sealInfoList.size();
        int loopNum = (totalSealNum % maxBatchSignNum == 0) ? totalSealNum / maxBatchSignNum : totalSealNum / maxBatchSignNum + 1;
        log.info("－消息－:requestSn={},分批处理签章,签章数据总量:{},最大盖章数为:{},分{}次盖完", requestSn, totalSealNum, maxBatchSignNum, loopNum);
        for (int index = 0; index < loopNum; index++) {
            int startIndex = index * maxBatchSignNum;
            int endIndex = (index + 1) * maxBatchSignNum > totalSealNum ? totalSealNum : (index + 1) * maxBatchSignNum;
            singleSign(requestSn, noSignPdfPath, signSnapPDFPath, sealInfoList.subList(startIndex, endIndex), isOfficial);
        }
        log.info("－消息－:requestSn={},分批处理个人签章完成", requestSn);
    }

    /**
     * 获取公章相关信息
     *
     * @param requestSn
     * @param templateNo
     * @return
     * @throws Exception
     */
    private List<CFCASignTO> getOfficalSealInfoList(String requestSn, String templateNo, List<String> sealCodeInfos) throws Exception {
        // 1、获取公章信息集合
        List<SealInfo> sealInfos;
        if (sealCodeInfos == null) {
            OfficialSealTemplate officialSealTemplate = new OfficialSealTemplate();
            officialSealTemplate.setTemplateNo(templateNo);
            List<OfficialSealTemplate> officialSealTemplates = officialSealTemplateService.selectByExample(officialSealTemplate);
            if (officialSealTemplates.isEmpty()) {
                log.error("－消息－:requestSn={},签章公章出现异常,该模板没有公章", requestSn);
                throw new Exception("模板没有公章");
            }
            List<String> sealCodeList = officialSealTemplates.stream().map(sealTemplate -> sealTemplate.getOfficialSealCode()).collect(Collectors.toList());
            sealInfos = sealInfoService.selectBySealCodeList(sealCodeList);
        } else {
            sealInfos = sealInfoService.selectBySealCodeList(sealCodeInfos);
        }

        // 2、检查公章状态
        List<CFCASignTO> collect = new ArrayList<>();
        for (SealInfo sealInfo : sealInfos) {
            if (StringUtils.isBlank(sealInfo.getKeyWords())) {
                log.error("－消息－:requestSn={},公章关键信息没有找到,请在系统配置管理中配置", requestSn);
                throw new RuntimeException("公章关键信息没有找到,请在系统配置管理中配置");
            }
            if (sealInfo.getStatus() == 0) {
                log.error("－消息－:requestSn={},公章状态不可用,请检查", requestSn);
                throw new RuntimeException("公章状态不可用,请检查");
            }
            CFCASignTO cfcaSignTO = generateCFCASignTO(requestSn, sealInfo);
            checkAndResumptionOfficialSealExpire(requestSn, sealInfo, cfcaSignTO);
            collect.add(cfcaSignTO);
        }
        return collect;
    }

    /**
     * 拼装CFCASignTO
     *
     * @param requestSn
     * @param sealInfo
     * @return
     * @throws Exception
     */
    private CFCASignTO generateCFCASignTO(String requestSn, SealInfo sealInfo) throws Exception {
        CFCASignTO cfcaSignTO = new CFCASignTO();
        cfcaSignTO.setKeyWord(sealInfo.getKeyWords());
        cfcaSignTO.setSealPassword(sealInfo.getSealPwd());
        cfcaSignTO.setSealName(sealInfo.getSealName());
        cfcaSignTO.setSealPerson(sealInfo.getSealPerson());
        cfcaSignTO.setUserName(sealInfo.getSealPerson());
        cfcaSignTO.setIdTypeCode(sealInfo.getIdentificationType());
        cfcaSignTO.setUserIdNo(sealInfo.getIdentificationNo());
        cfcaSignTO.setOrganCode(sealInfo.getSealOrg());
        cfcaSignTO.setSealImage(sealInfo.getImage());
        if (StringUtils.isBlank(sealInfo.getSealCode())) {
            log.error("－消息－:requestSn={},公章不存在", requestSn);
            throw new Exception("章不存在");
        }
        cfcaSignTO.setSealCode(sealInfo.getSealCode());
        return cfcaSignTO;
    }

    /**
     * 校验公章是否过期，若过期重新生成
     *
     * @param requestSn
     * @param sealInfo
     * @param cfcaSealInfoTO
     * @throws Exception
     */
    private void checkAndResumptionOfficialSealExpire(String requestSn, SealInfo sealInfo, CFCASignTO cfcaSealInfoTO) throws Exception {
        SealCertInfoRo sealCertInfo = cfcaSignService.querySealInfo(requestSn, sealInfo.getSealCode());
        String certNotAfter = sealCertInfo.getCertNotAfter();
        int i = certNotAfter.compareTo(DateUtil.getStandardDateTimeStr());
        if (i < 0) {
            // 执行更新操作
            updateOfficeSeal(requestSn, cfcaSealInfoTO);
            Cert cert = new Cert();
            cert.setCertDn(sealCertInfo.getCertDN());
            cert.setCertSn(sealCertInfo.getCertSN());
            cert.setApplyTime(DateUtil.getDateFromStr2(sealCertInfo.getCertNotBefore()));
            cert.setExpireTime(DateUtil.getDateFromStr2(sealCertInfo.getCertNotAfter()));
            cert.setUpdateTime(DateUtil.getDateFromStr2(sealCertInfo.getCertNotBefore()));
            cert.setId(Long.valueOf(sealInfo.getCertId()));
            certMapper.updateByPrimaryKeySelective(cert);

            sealInfo.setUpdateTime(new Date());
            Example example = new Example(SealInfo.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("sealCode", sealInfo.getSealCode());
            sealInfoMapper.updateByExampleSelective(sealInfo, example);
        }
    }

    /**
     * 校验公章是否过期，若过期重新生成
     *
     * @param requestSn
     * @param cfcaSealInfoTO
     * @return
     * @throws Exception
     */
    private SealCertInfoRo updateOfficeSeal(String requestSn, CFCASignTO cfcaSealInfoTO) throws Exception {
        CFCASignResult cfcaSignResult = cfcaSignService.updateOfficeSeal(cfcaSealInfoTO, requestSn);
        if (!cfcaSignResult.getIsSuccess()) {
            log.error("－消息－:requestSn={},签章结果:{}", requestSn, cfcaSignResult.getResultMsg());
            signatureLogService.updateResult(cfcaSignResult.getResultMsg(), requestSn);
            throw new RuntimeException("调用接口更新公章失败......" + cfcaSignResult.getResultMsg());
        }
        List<SealCertInfoRo> sealCertInfoRoList = cfcaSignResult.getSealCertInfoRoList();
        return sealCertInfoRoList.get(0);
    }

    /**
     * 获取私章相关信息
     *
     * @param certInfos
     * @return
     */
    private List<CFCASignTO> getPersonalSealInfoList(List<Map<String, Object>> certInfos, String requestSn) throws Exception {
        List<CFCASignTO> collect = certInfos.parallelStream().map(jsonObj -> {
            CertApplyVO applyVO = JSONObject.parseObject(JSONObject.toJSONString(jsonObj), CertApplyVO.class);
            // 1、实际中发现有的系统传idCardTypeCode，有的系统传idTypeCode，需要做非空判断
            String idCardTypeCode = (String) jsonObj.get("idCardTypeCode");
            if (StringUtils.isNotBlank(idCardTypeCode)) {
                applyVO.setIdTypeCode(idCardTypeCode);
            }
            String idTypeCode = applyVO.getIdTypeCode();
            if (StringUtils.isBlank(idTypeCode)) {
                applyVO.setIdTypeCode("0");
            }
            CFCASignTO cfcaSignTO = new CFCASignTO();
            BeanUtils.copyProperties(applyVO, cfcaSignTO);

            // 2、执行制章操作
            SealApplyInfoDTO sealApplyInfo = certInfoService.getUnExpireSealInfo(cfcaSignTO.getIdTypeCode(), cfcaSignTO.getUserIdNo(), cfcaSignTO.getUserName());
            if (sealApplyInfo == null) {
                SealApplyInfoDTO sealApplyInfo2 = certInfoService.getSealInfo(cfcaSignTO.getIdTypeCode(), cfcaSignTO.getUserIdNo(), cfcaSignTO.getUserName());
                if (sealApplyInfo2 != null) {
                    // 数据存在,说明数据过期：执行更新操作
                    cfcaSignTO.setSealFlag(sealApplyInfo2.getSealFlag());
                    updatePersonalSeal(cfcaSignTO, requestSn);
                } else {
                    // 数据不存在，执行重新申请操作--【可以加分布式锁，解决印章申请信息（证件类型-证件号-印章标识）重复的错误】
                    makePersonalSeal(cfcaSignTO, requestSn);
                }
            } else {
                cfcaSignTO.setSealCode(sealApplyInfo.getSealCode());
                cfcaSignTO.setSealPassword(sealApplyInfo.getSealPassword());
            }
            return cfcaSignTO;
        }).collect(Collectors.toList());

        return collect;
    }

    /**
     * 制作私章
     *
     * @param cfcaSignTO
     * @param requestSn
     * @throws Exception
     */
    private void makePersonalSeal(CFCASignTO cfcaSignTO, String requestSn) {
        CFCASignResult cfcaSignResult = null;
        try {
            cfcaSignResult = cfcaSignService.batchMakePersonalSeal(cfcaSignTO, requestSn);
        } catch (Exception e) {
            log.error("执行申请私章失败......", e);
        }
        if (!cfcaSignResult.getIsSuccess()) {
            signatureLogService.updateResult(cfcaSignResult.getResultMsg(), requestSn);
            throw new RuntimeException("申请私章失败......" + cfcaSignResult.getResultMsg());
        } else {
            signatureLogService.updateResult("申请私章成功", requestSn);
            signatureLogService.insertPersonalSeal(cfcaSignTO, requestSn);
        }
    }

    /**
     * 更新私章
     *
     * @param requestSn
     * @param cfcaSignTO
     */
    private void updatePersonalSeal(CFCASignTO cfcaSignTO, String requestSn) {
        CFCASignParam cfcaSignParam = new CFCASignParam();
        cfcaSignParam.setRequestSn(requestSn);
        cfcaSignParam.setCfcaSignTO(cfcaSignTO);
        CFCASignResult cfcaSignResult = null;
        try {
            cfcaSignResult = cfcaSignService.updatePersonalSeal(cfcaSignTO, requestSn);
        } catch (Exception e) {
            log.error("执行更新私章失败......", e);
        }
        if (!cfcaSignResult.getIsSuccess()) {
            signatureLogService.updateResult(cfcaSignResult.getResultMsg(), requestSn);
            throw new RuntimeException("更新私章失败......" + cfcaSignResult.getResultMsg());
        } else {
            signatureLogService.updateResult("更新私章成功", requestSn);
            certInfoService.updateCertDate(requestSn, cfcaSignParam.getCfcaSignTO());
        }
    }

    /**
     * 获取要签章pdf文件路径
     *
     * @param fileName
     * @param date
     * @return
     */
    private String getSignSnapPDFPath(String fileName, String date) {
        File snapPDFDictory = new File(snapPDFPath + File.separator + date);
        if (!snapPDFDictory.exists()) {
            snapPDFDictory.mkdirs();
        }
        return snapPDFPath + File.separator + date + File.separator + fileName;
    }
}
